//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#define __NO_LINKNODE_CONSTRUCTOR
#include <mantle.h>

EXTERN_C void CDECL initenv(wchar_t **ppwszEnvBase);
UInt32 LoadStartup();

EXTERN_C void CDECL MantleEntry(void)
{
    TESTENTRY_MANTLE_ENTRY

    initenv((wchar_t **)USER_ENVIRONMENT_BASE);

    UInt32 uRebootTime = LoadStartup();
    if (uRebootTime != (UInt32)INFINITE) {
        kprintf("\nSystem will automatic reboot after %d second(s).\n",
            uRebootTime);
    }

    DzSleep(DzMillisecondsToTicks(1000) * uRebootTime, NULL);

    TESTENTRY_KERNEL_EXIT

    kprintf("Rebooting ...\n");
    TriggerSystemEvent(SystemEvent_Shutdown);
    DzSleep(DzMillisecondsToTicks(1000), NULL);
    StopPeriodicTasks();
    BspReboot();
}

static char *s_pImage, *s_pImageLimit;
static wchar_t s_wszCurrent[35];
static bool_t   s_bBackGround = FALSE;

typedef enum Token {
    Token_Symbol = 0x01,
    Token_EndOfLine,
    Token_EndOfFile,
}   Token;

typedef enum ParseState {
    ParseState_ReadCommand = 0x01,
    ParseState_ReadArg,
    ParseState_ReadRebootTime,
    ParseState_SkipLine,
}   ParseState;

#define STARTUP_FILE            L"startup.cfg"
#define DEFAULT_REBOOT_TIME     10

#define IS_BLANK(c)     ((c) == ' ' || (c) == '\t')
#define IS_EOL(c)       ((c) == '\n' || (c) == '\r')
#define IS_EOF(c)       (s_pImage > s_pImageLimit || (c) == '\0')

Token GetAToken()
{
    int n = 0;

    if (IS_EOF(*s_pImage)) return Token_EndOfFile;

    //
    // Skip blank
    //
    while (IS_BLANK(*s_pImage)) {
        s_pImage++;
        if (IS_EOF(*s_pImage)) return Token_EndOfFile;
    }

    //
    // Is end of line ?
    //
    if (IS_EOL(*s_pImage)) {
        do {
            s_pImage++;
            if (IS_EOF(*s_pImage)) return Token_EndOfFile;
        } while (IS_EOL(*s_pImage));
        return Token_EndOfLine;
    }

    //
    // That's must be a symbol
    //
    do {
        if ('&' == *s_pImage) {
            s_bBackGround = TRUE;
        }

        if (!s_bBackGround) {
            s_wszCurrent[n++] = (wchar_t)*s_pImage;
        }
        s_pImage++;
    } while (!IS_EOF(*s_pImage) && !IS_EOL(*s_pImage) && !IS_BLANK(*s_pImage));

    s_wszCurrent[n] = L'\0';
    return Token_Symbol;
}

void CreateProcess(WString, WString);
UInt32 NumberFromString(wchar_t *);
ELAPI _CProfile_GetInt32(AString appName, AString keyName, Int32 *pValue);

UInt32 LoadStartup()
{
    UInt32 uRet = 1;
    Token token;
    ParseState state;
    WStringBuf_<33> esbCommand;
    WStringBuf_<67> esbArg;

    _CProfile_GetInt32("MemoryManagement", "LowMemoryThreshold",
        &g_LowMemoryThreshold);
    _CProfile_GetInt32("MemoryManagement", "CriticallyLowMemoryThreshold",
        &g_CriticallyLowMemoryThreshold);
    _CProfile_GetInt32("MemoryManagement", "HeapGrow1stThreshold",
        &g_HeapGrow1stThreshold);
    _CProfile_GetInt32("MemoryManagement", "HeapGrow2ndThreshold",
        &g_HeapGrow2ndThreshold);
    _CProfile_GetInt32("MemoryManagement", "RestoreHeapGrowThreshold",
        &g_RestoreHeapGrowThreshold);
    _CProfile_GetInt32("MemoryManagement", "UserStackGrowThreshold",
        &g_UserStackGrowThreshold);

    ECode ec = GetBootModule(
            STARTUP_FILE, (void **)&s_pImage, (void **)&s_pImageLimit);
    if (FAILED(ec)) {
        kprintf("Mantle Entry: can't found [startup.cfg].\n");
        return DEFAULT_REBOOT_TIME;
    }

    state = ParseState_ReadCommand;
    while (1) {
        token = GetAToken();

        if (Token_Symbol == token) {
            switch (state) {
                case ParseState_ReadCommand:
                    if (!wcscmp(s_wszCurrent, L"reboot")) {
                        state = ParseState_ReadRebootTime;
                    }
                    else {
                        esbCommand.Copy(s_wszCurrent);
                        esbArg.Copy(L"");
                        state = ParseState_ReadArg;
                    }
                    break;

                case ParseState_ReadArg:
                    esbArg.Append(L" ");
                    esbArg.Append(s_wszCurrent);
                    break;

                case ParseState_ReadRebootTime:
                    uRet = NumberFromString(s_wszCurrent);
                    state = ParseState_SkipLine;
                    break;

                default:
                    assert(state == ParseState_SkipLine);
                    break;
            }
        }
        else {
            assert(token == Token_EndOfLine || token == Token_EndOfFile);
            switch (state) {
                case ParseState_ReadArg:
                    CreateProcess(esbCommand, esbArg);
                    break;

                default:    // ReadCommand || ReadRebootTime || SkipLine
                    break;  // Do nothing but break out
            }

            if (token == Token_EndOfFile) {
                break;
            }
            else {
                state = ParseState_ReadCommand;
            }
        }
    }

    ReclaimBootModule(STARTUP_FILE);

    return 0 == uRet ? (UInt32)INFINITE : uRet;
}

void CreateProcess(WString wsName, WString wsArg)
{
    CProcess *pProcess;
    ECode ec;

    kprintf("Creating process %S, arg <%S> ... ",
        (const wchar_t *)wsName, (const wchar_t *)wsArg);

    pProcess = new CProcess();
    if (NULL == pProcess) {
        ec = E_OUT_OF_MEMORY;
    }
    else {
        pProcess->AddRef();
        ec = pProcess->Start(wsName, wsArg);
    }

    WaitResult wr;
    if (SUCCEEDED(ec)) {
        kprintf("Ok.\n");
        if (!s_bBackGround) {
            ec = pProcess->WaitForExit(INFINITE, &wr);
            assert(NOERROR == ec && WaitResult_OK == wr);
        }
        else {
            s_bBackGround = FALSE;
        }
    }
    else {
        kprintf("Failed, ec = %x\n", ec);
    }
    if (pProcess) pProcess->Release();
}

#define IS_NUMBER(c)    ((c) >= L'0' && (c) <= L'9')

UInt32 NumberFromString(wchar_t *pString)
{
    UInt32 n = 0;

    while (*pString) {
        if (!IS_NUMBER(*pString)) break;

        n = (*pString - L'0') + n * 10;
        pString++;
    }

    return n;
}
